package com.changge.common.security.utils;

import com.changge.common.core.utils.StringUtil;
import com.changge.common.security.enums.TokenValidateEnum;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.changge.common.core.constant.TimeConst.TWO_HOUR;


/**
 * Jwt处理Token工具类
 *
 * @author zhangrongkang
 * @since  2023/3/10
 */
@Component
@Slf4j
public class JwtTokenUtil {
    /**
     * 用户名的key
     */
    private static final String CLAIM_KEY_SUB = "sub";
    /**
     * JWT的创建时间
     */
    private static final String CLAIM_KEY_CREATED = "created";
    /**
     * 秘钥
     */
    @Value("${jwt.secret}")
    private String secret;

    /**
     * 根据用户信息生成token
     *
     * @param traceId 用户追踪值唯一标识
     * @return 生成的token
     */
    public String generateToken(String traceId) {
        // 定义token中存储数据的载荷
        Map<String, Object> claims = new HashMap<>();
        // 1. 用户名
        claims.put(CLAIM_KEY_SUB, traceId);
        // 2. 签发时间
        claims.put(CLAIM_KEY_CREATED, new Date());
        // 签发token
        return generateToken(claims);
    }

    /**
     * 根据载荷生成token
     *
     * @param claims 载荷
     * @return 生成的token
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                // 1. 设置载荷
                .setClaims(claims)
                // 2. 设置失效时间
                .setExpiration(generateExpirationDate())
                // 3. 设置签名
                .signWith(SignatureAlgorithm.HS512, secret)
                // 签发token
                .compact();
    }

    /**
     * 生成token失效时间
     *
     * @return token的失效时间
     */
    private Date generateExpirationDate() {
        // token失效时间：当前系统时间 + 2小时
        return new Date(System.currentTimeMillis() + TWO_HOUR * 1000);
    }

    /**
     * 从token中获取唯一标识
     *
     * @param token token
     * @return 当前token中存储的登录追踪值
     */
    public String getTraceIdFromToken(String token) {
        String traceId = null;
        try {
            // 从token中获取到载荷
            Claims claims = getClaimsFromToken(token);
            // 通过载荷获取到登录追踪值
            if (claims != null) {
                traceId = claims.getSubject();
            }
        } catch (Exception e) {
            // 如果出现异常则将 traceId 设置为空
            log.error("解析traceId出错, 原因: {}", e.getMessage());
        }
        return traceId;
    }

    /**
     * 从token中获取载荷
     *
     * @param token token
     * @return token中的载荷
     */
    private Claims getClaimsFromToken(String token) {
        if (StringUtil.isBlank(token)) {
            return null;
        }
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    // 解密的秘钥
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            if (e instanceof ExpiredJwtException) {
                return ((ExpiredJwtException) e).getClaims();
            }
            return null;
        }
        return claims;
    }

    /**
     * 校验token并返回校验结果
     *
     * @param token token
     * @return tokenValidate Token校验结果
     * @see TokenValidateEnum
     */
    public TokenValidateEnum validateToken(String token) {
        // 校验token格式是否合法
        Claims claims = getClaimsFromToken(token);
        if (null == claims) {
            return TokenValidateEnum.ILLEGAL;
        }
        // 获取token过期时间
         Date expiredDate = getTokenExpiredFromClaims(claims);
        if (expiredDate.before(new Date())) {
            return TokenValidateEnum.EXPIRE;
        }
        return TokenValidateEnum.ACCESS;
    }

    /**
     * 获取token过期时间
     *
     * @param claims token载荷
     * @return token过期时间
     */
    private Date getTokenExpiredFromClaims(Claims claims) {
        return claims.getExpiration();
    }

}
